﻿using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;

namespace Game.FlowField.Burst
{
    [BurstCompile]
    public struct UnitMovingJob : IJob
    {
        public NativeArray<bool> GridIsBlocks;

        [ReadOnly]
        public NativeArray<int> GridWeights;

        public NativeArray<int> GridOccupies;

        [ReadOnly]
        public NativeArray<float2> GridPositions;

        /// <summary>
        /// Input Output
        /// </summary>
        public NativeArray<Unit> Units;

        /// <summary>
        /// Output
        /// </summary>
        public NativeArray<int2> CenterIndex2D;

        [ReadOnly]
        public float Speed;

        [ReadOnly]
        public int2 MapSize;

        [ReadOnly]
        public int2 TargetIndex2D;

        [ReadOnly]
        public int AliveUnitCount;

        [BurstDiscard]
        public void WrapData(MapData mapData, NativeArray<Unit> units, int aliveUnitCount, NativeArray<int2> centerIndex2D, float speed)
        {
            this.GridIsBlocks = mapData.GridIsBlocks;
            this.GridWeights = mapData.GridWeights;
            this.GridOccupies = mapData.GridOccupies;
            this.GridPositions = mapData.GridPositions;
            this.MapSize = mapData.Size;
            this.TargetIndex2D = mapData.TargetInfo.Index2D;
            this.Units = units;
            this.Speed = speed;
            this.AliveUnitCount = aliveUnitCount;
            this.CenterIndex2D = centerIndex2D;
        }

        [BurstCompile]
        public void Execute()
        {
            var newCenterIndex2D = new int2(0, 0);
            var mapSize = this.MapSize;
            var centerIndex2D = this.CenterIndex2D[0];
            var trendDiretion = this.TargetIndex2D - centerIndex2D;
            var finalTrendDiretion = math.round(math.normalize(trendDiretion) * 0.5f / 0.707f/* + math.sign(trendDiretion) * 0.1f*/);
            var shouldTrend = trendDiretion.x * trendDiretion.x + trendDiretion.y * trendDiretion.y > math.max(AliveUnitCount * 0.001f, 2f);
            trendDiretion = shouldTrend ? new int2(math.sign(finalTrendDiretion)) : new int2(0, 0);

            var aliveCount = 0;
            for (int i = 0; i < this.Units.Length; i++)
            {
                var unit = this.Units[i];
                if (unit.IsAlive)
                {
                    switch (unit.Motion)
                    {
                        case UnitMotion.Stay:
                            this.ExecuteStay(ref unit, ref trendDiretion);
                            break;
                        case UnitMotion.Moving:
                            this.ExecuteMoving(ref unit);
                            break;
                    }
                    newCenterIndex2D += MapDataUtil.ConvertIndex2Index2D(unit.GridIndexTo, mapSize);
                    this.Units[i] = unit;
                    aliveCount++;
                }
            }

            if(aliveCount > 0)
            {
                this.CenterIndex2D[0] = newCenterIndex2D / aliveCount;
            }
        }

        [BurstCompile]
        private void ExecuteStay(ref Unit unit,ref int2 trendDiretion)
        {
            var index = unit.GridIndexTo;
            var index2D = MapDataUtil.ConvertIndex2Index2D(index, this.MapSize);
            var trendWeightPoint = new WeightPoint();
            var trendProgressFactor = 1f;
            var bestWeightPoint = new WeightPoint(){Weight = this.GridWeights[index]};
            var movingProgressFactor = 1f;
            var center2UnitDiretion = MapDataUtil.ConvertIndex2Index2D(unit.GridIndexTo, this.MapSize) - this.CenterIndex2D[0];
            var finalTrendDiretion = math.round(math.normalize(center2UnitDiretion) * 0.5f / 0.707f/* + math.sign(center2UnitDiretion) * 0.1f*/);
            center2UnitDiretion = new int2(math.sign(finalTrendDiretion));
            var shouldTrend = center2UnitDiretion.x == trendDiretion.x && center2UnitDiretion.y == trendDiretion.y;
            // 上
            var weightPoint0 = new WeightPoint();
            weightPoint0.Index2D = index2D + new int2(0, 1);
            if (MapDataUtil.IsInsideMap(weightPoint0.Index2D, this.MapSize))
            {
                weightPoint0.IsValidPoint = true;
                weightPoint0.Index = index + 1;
                weightPoint0.Weight = this.GridWeights[weightPoint0.Index];

                if (!this.GridIsBlocks[weightPoint0.Index] &&
                    this.GridOccupies[weightPoint0.Index] < 1) 
                { 
                    if(weightPoint0.Weight <= bestWeightPoint.Weight)
                    {
                        bestWeightPoint = weightPoint0;
                        movingProgressFactor = 1f;
                    }
                    else if (shouldTrend && trendDiretion.x == 0 && trendDiretion.y == 1)
                    {
                        trendWeightPoint = weightPoint0;
                        trendProgressFactor = 1f;
                    }
                }
            }

            // 下
            var weightPoint1 = new WeightPoint();
            weightPoint1.Index2D = index2D + new int2(0, -1);
            if (MapDataUtil.IsInsideMap(weightPoint1.Index2D, this.MapSize))
            {
                weightPoint1.IsValidPoint = true;
                weightPoint1.Index = index - 1;
                weightPoint1.Weight = this.GridWeights[weightPoint1.Index];

                if (!this.GridIsBlocks[weightPoint1.Index] &&
                    this.GridOccupies[weightPoint1.Index] < 1)
                {
                    if (weightPoint1.Weight <= bestWeightPoint.Weight)
                    {
                        bestWeightPoint = weightPoint1;
                        movingProgressFactor = 1f;
                    }
                    else if (shouldTrend && trendDiretion.x == 0 && trendDiretion.y == -1)
                    {
                        trendWeightPoint = weightPoint1;
                        trendProgressFactor = 1f;
                    }
                }
            }

            // 左
            var weightPoint2 = new WeightPoint();
            weightPoint2.Index2D = index2D + new int2(-1, 0);
            if (MapDataUtil.IsInsideMap(weightPoint2.Index2D, this.MapSize))
            {
                weightPoint2.IsValidPoint = true;
                weightPoint2.Index = index - this.MapSize.y;
                weightPoint2.Weight = this.GridWeights[weightPoint2.Index];

                if (!this.GridIsBlocks[weightPoint2.Index] &&
                    this.GridOccupies[weightPoint2.Index] < 1)
                {
                    if (weightPoint2.Weight <= bestWeightPoint.Weight)
                    {
                        bestWeightPoint = weightPoint2;
                        movingProgressFactor = 1f;
                    }
                    else if (shouldTrend && trendDiretion.x == -1 && trendDiretion.y == 0)
                    {
                        trendWeightPoint = weightPoint2;
                        trendProgressFactor = 1f;
                    }
                }
            }

            // 右
            var weightPoint3 = new WeightPoint();
            weightPoint3.Index2D = index2D + new int2(1, 0);
            if (MapDataUtil.IsInsideMap(weightPoint3.Index2D, this.MapSize))
            {
                weightPoint3.IsValidPoint = true;
                weightPoint3.Index = index + this.MapSize.y;
                weightPoint3.Weight = this.GridWeights[weightPoint3.Index];

                if (!this.GridIsBlocks[weightPoint3.Index] &&
                    this.GridOccupies[weightPoint3.Index] < 1)
                {
                    if (weightPoint3.Weight <= bestWeightPoint.Weight)
                    {
                        bestWeightPoint = weightPoint3;
                        movingProgressFactor = 1f;
                    }
                    else if (shouldTrend && trendDiretion.x == 1 && trendDiretion.y == 0)
                    {
                        trendWeightPoint = weightPoint3;
                        trendProgressFactor = 1f;
                    }
                }
            }

            // 左上
            var weightPoint4 = new WeightPoint();
            weightPoint4.Index2D = index2D + new int2(-1, 1);
            if (MapDataUtil.IsInsideMap(weightPoint4.Index2D, this.MapSize))
            {
                weightPoint4.IsValidPoint = true;
                weightPoint4.Index = index - this.MapSize.y + 1;
                weightPoint4.Weight = this.GridWeights[weightPoint4.Index];

                if (!this.GridIsBlocks[weightPoint4.Index] &&
                    this.GridOccupies[weightPoint4.Index] < 1) 
                {
                    if (weightPoint4.Weight <= bestWeightPoint.Weight)
                    {
                        bestWeightPoint = weightPoint4;
                        movingProgressFactor = 1f / math.SQRT2;
                    }
                    else if (shouldTrend && trendDiretion.x == -1 && trendDiretion.y == 1)
                    {
                        trendWeightPoint = weightPoint4;
                        trendProgressFactor = 1f / math.SQRT2;
                    }
                }
            }

            // 右上
            var weightPoint5 = new WeightPoint();
            weightPoint5.Index2D = index2D + new int2(1, 1);
            if (MapDataUtil.IsInsideMap(weightPoint5.Index2D, this.MapSize))
            {
                weightPoint5.IsValidPoint = true;
                weightPoint5.Index = index + this.MapSize.y + 1;
                weightPoint5.Weight = this.GridWeights[weightPoint5.Index];

                if (!this.GridIsBlocks[weightPoint5.Index] &&
                    this.GridOccupies[weightPoint5.Index] < 1)
                {
                    if (weightPoint5.Weight <= bestWeightPoint.Weight)
                    {
                        bestWeightPoint = weightPoint5;
                        movingProgressFactor = 1f / math.SQRT2;
                    }
                    else if (shouldTrend && trendDiretion.x == 1 && trendDiretion.y == 1)
                    {
                        trendWeightPoint = weightPoint5;
                        trendProgressFactor = 1f / math.SQRT2;
                    }
                }
            }

            // 左下
            var weightPoint6 = new WeightPoint();
            weightPoint6.Index2D = index2D + new int2(-1, -1);
            if (MapDataUtil.IsInsideMap(weightPoint6.Index2D, this.MapSize))
            {
                weightPoint6.IsValidPoint = true;
                weightPoint6.Index = index - this.MapSize.y - 1;
                weightPoint6.Weight = this.GridWeights[weightPoint6.Index];

                if (!this.GridIsBlocks[weightPoint6.Index] &&
                    this.GridOccupies[weightPoint6.Index] < 1)
                {
                    if (weightPoint6.Weight <= bestWeightPoint.Weight)
                    {
                        bestWeightPoint = weightPoint6;
                        movingProgressFactor = 1f / math.SQRT2;
                    }
                    else if (shouldTrend && trendDiretion.x == -1 && trendDiretion.y == -1)
                    {
                        trendWeightPoint = weightPoint6;
                        trendProgressFactor = 1f / math.SQRT2;
                    }
                }
            }

            // 左下
            var weightPoint7 = new WeightPoint();
            weightPoint7.Index2D = index2D + new int2(1, -1);
            if (MapDataUtil.IsInsideMap(weightPoint7.Index2D, this.MapSize))
            {
                weightPoint7.IsValidPoint = true;
                weightPoint7.Index = index + this.MapSize.y - 1;
                weightPoint7.Weight = this.GridWeights[weightPoint7.Index];

                if (!this.GridIsBlocks[weightPoint7.Index] &&
                    this.GridOccupies[weightPoint7.Index] < 1)
                {
                    if (weightPoint7.Weight <= bestWeightPoint.Weight)
                    {
                        bestWeightPoint = weightPoint7;
                        movingProgressFactor = 1f / math.SQRT2;
                    }
                    else if (shouldTrend && trendDiretion.x == 1 && trendDiretion.y == -1)
                    {
                        trendWeightPoint = weightPoint7;
                        trendProgressFactor = 1f / math.SQRT2;
                    }
                }
            }

            if(!bestWeightPoint.IsValidPoint)
            {
                bestWeightPoint = trendWeightPoint;
                movingProgressFactor = trendProgressFactor;
            }

            if (bestWeightPoint.IsValidPoint)
            {
                this.GridOccupies[bestWeightPoint.Index] = this.GridOccupies[bestWeightPoint.Index] + 1;
                unit.GridIndexFrom = unit.GridIndexTo;
                unit.GridIndexTo = bestWeightPoint.Index;
                unit.MovingProgress = 0f;
                unit.MovingProgressFactor = movingProgressFactor;
                unit.PositionFrom = this.GridPositions[unit.GridIndexFrom];
                unit.Direction = this.GridPositions[unit.GridIndexTo] - unit.PositionFrom;
                unit.Motion = UnitMotion.Moving;
                this.GridOccupies[unit.GridIndexFrom] = this.GridOccupies[unit.GridIndexFrom] - 1;

                this.ExecuteMoving(ref unit);
            }
        }

        [BurstCompile]
        private void ExecuteMoving(ref Unit unit)
        {
            if (unit.MovingProgress >= 1f)
            {
                unit.Motion = UnitMotion.Stay;
                return;
            }
            
            unit.MovingProgress = math.clamp(unit.MovingProgress + this.Speed * unit.MovingProgressFactor, 0f, 1f);
            unit.Position = unit.PositionFrom + unit.Direction * unit.MovingProgress;
        }
    }
}